/* 
 * File:   PhysicsManager.cpp
 * Author: RedEyedKiller
 * 
 * Created on 26 Οκτώβριος 2010, 1:59 πμ
 */

#define DEBUG_PHYSICS

#include <assert.h>
#include <sstream>
#include <algorithm>
#include <iostream>
#include "PhysicsManager.h"
#include "../TileProperties.h"
#include "../Logger.h"
#include "../TimedUpdater.h"
#include "CollisionHandler.h"
#include "EntityContactInfo.h"

namespace physicsSystem
{

using namespace PhysicsInternals;


PhysicsManager::PhysicsManager() : collisionHandler(0)
{
    currentMap = NULL;
}

void PhysicsManager::SetCurrentMap(LevelMap* currentMap)
{
    this->currentMap = currentMap;
    particlesUpdateTime = 20;
}

LevelMap* PhysicsManager::GetCurrentMap() const
{
    return currentMap;
}

PhysicsManager::~PhysicsManager()
{
}

/**
 * The main method of PhysicsManager. All per frame operations are invoked through
 * this method.
 */
void PhysicsManager::Update(unsigned int deltaTime)
{
    if( deltaTime > ( 1000 / 60 ) )
        deltaTime = ( 1000 / 60 );
    //keep in track the time in order to update correctly

    static unsigned long moments = 0;
    moments += deltaTime;

    ApplyForces( deltaTime / 1000.f );

    if( moments > ( unsigned ) particlesUpdateTime )
    {
        UpdateParticles( moments, currentMap );
        moments = 0;
    }

    UpdatePhysicsPool( deltaTime );
    collisionHandler.CollisionCheck( physicsPool, currentMap, tileProperties, deltaTime / 1000.0f );
}

/**
 * Adds an existent physicsLogic object at the end of physicsPool.
 * @param toAdd - The object to add. Note that if removed from the physicsPool
 * it will NOT be deleted so you must keep its pointer in an entity.
 */
void PhysicsManager::AddPhysicsLogic(EntitySystem::PhysicsLogic* toAdd)
{
    //toAdd->SetGeneralAcceleration(Math::Vector2F(0, 9));
    toAdd->SetPhysicsManager( this );
    
    physicsPool.push_back( toAdd );
}

void PhysicsManager::RemovePhysicsLogic(EntitySystem::PhysicsLogic* toRemove)
{
    //find where toRemove is located
    PhysicsPool::iterator it = std::find( physicsPool.begin( ), physicsPool.end( ), toRemove );
    if( it == physicsPool.end( ) )
    {
        std::cerr << "Error removing not existant physics logic" << std::endl;
    }

    PhysicsLogic* p = *( it );
    EntityContactInfo* i = p->GetNextContact( );
    while( i )
    {
        i->GetCollidedWith( )->GetPhysicsLogic( )->DeleteContact( p->GetParent( ) );
        i = p->GetNextContact( );
    }

    std::iter_swap( it, physicsPool.end( ) - 1 ); //send the toRemove object to the end of the pool
    physicsPool.pop_back( ); //and pop it
}

void PhysicsManager::SetTileProperties(TileProperties* tileProperties)
{
    this->tileProperties = tileProperties;
}

const TileProperties* PhysicsManager::GetTileProperties() const
{
    return tileProperties;
}

//bool PhysicsManager::TileTouch(int tileX, int tileY, const Math::Rect* rect)
//{
//    return tileX * currentMap->GetTileWidth() == rect->GetTotalWidth() ||
//            (tileX + 1) * currentMap->GetTileWidth() == rect->GetLeft() ||
//            tileY * currentMap->GetTileHeight() == rect->GetTotalHeight() ||
//            (tileY + 1) * currentMap->GetTileHeight() == rect->GetTop();
//
//}

/**
 * This method iterates through all global forces of the system and affects all
 * physical objects (physicsLogic)
 */
void PhysicsManager::ApplyForces(float seconds)
{
    //force iterators
    GlobalForcesPool::iterator force = gForcePool.begin( );
    const GlobalForcesPool::iterator endForce = gForcePool.end( );
    //physicsObject iterators.
    PhysicsPool::iterator object;
    const PhysicsPool::iterator endObject = physicsPool.end( );
    //particle systems iterators
    ParticleSystemPool::iterator particleSystem;
    const ParticleSystemPool::iterator partSystemsEnd = systems.end( );

    particleSystem::ParticleSystem* curSystem;

    //for each force
    for(; force != endForce; ++force )
    {
        forces::TimedForce* tForce = force->second;
        //update it based on time
        tForce->Prepare( seconds );
        //and apply it on all physical objects
        for( object = physicsPool.begin( ); object != endObject; ++object )
        {
            //make sure body doesnt have infinite mass
            if( ( *object )->HasInfiniteMass( ) )
                continue;
            tForce->Affect( *object );
        }
        //and all particle systems
        for( particleSystem = systems.begin( ); particleSystem != partSystemsEnd; ++particleSystem )
        {
            curSystem = particleSystem->second;
            if( curSystem->IsActive( ) )
            {
                curSystem->ApplyForcesToParticles( tForce );
            }
        }
    }
}

void PhysicsManager::AddGlobalForce(forces::TimedForce* force, const std::string& id)
{
    gForcePool[id] = force;
}

/**
 * Updates al objects of PhysicsPool by the given time.
 * @param time
 */
void PhysicsManager::UpdatePhysicsPool(unsigned int time)
{
    float seconds = time / 1000.0f;
    PhysicsPool::iterator it = physicsPool.begin( );
    PhysicsPool::iterator end = physicsPool.end( );
    for(; it != end; ++it )
    {
        ( *it )->Update( seconds );
    }
}


/**
 * Initializes some parameters of the comparison.
 *
 * @param xAxis - sorting will occur based on x axis positions if true, y axis positions if false.
 * @param ascending - sorting using this functor will result in an ascending order of the physics objects.
 */

ComparePhysicsObjects::ComparePhysicsObjects(bool xAxis, bool ascending)
:
m_xAxis(xAxis),
m_ascending(ascending)
{
}

bool ComparePhysicsObjects::operator( ) ( PhysicsLogic *left, PhysicsLogic *right )
{
    Vector2F leftPos = left->GetPosition( );
    Vector2F rightPos = right->GetPosition( );

    if( m_xAxis )
    {
        if( m_ascending )
        {
            return leftPos.GetX( ) < rightPos.GetX( );
        }

        return leftPos.GetX( ) > rightPos.GetX( );
    }
    else
    {
        if( m_ascending )
        {
            return leftPos.GetY( ) < rightPos.GetY( );
        }

        return leftPos.GetY( ) > rightPos.GetY( );
    }
}

};
